﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using DomainDriver.DomainModeling.DataProviders;
using DomainDriver.DomainModeling.Repositories;
using DomainDriver.DomainModeling.StorageManagers;
using DomainDriver.DomainModeling.StorageManagers.DataSetStorage;
using DomainDriver.DomainModeling.StorageManagers.EntityFrameworkStorage;

namespace DomainDriver.DomainModeling.DomainModels.ConstructionResources
{
    public class RepositoryConstructionMethods
    {
        public static object CreateTransientBackedRepository(Type dotNetKeyType, Type databaseKeyType, Type domainObjectType, IDataProvider dataProvider, Expression domainObjectConstructionMethod, IDomainModel domainModel)
        {
            var entry = ConstructorCache.Current.GetEntry(dotNetKeyType, databaseKeyType, domainObjectType);
            if ((entry == null) || (!entry.HasConstructor_Transient))
            {
                Type genericRepositoryType = typeof(Repository<,>);
                Type genericStorageManagerInterfaceType = typeof(IStorageManager<,>);
                Type genericStorageManagerType = typeof(TransientStorageManager<,>);
                Type dataProviderType = typeof(IDataProvider);
                Type dataSourceType = dataProvider.DataSourceType;

                // Get IStorageManagerType
                Type[] genericStorageManagerInterfaceParameters = new Type[] { dotNetKeyType, domainObjectType };
                Type storageManagerInterfaceType = genericStorageManagerInterfaceType.MakeGenericType(genericStorageManagerInterfaceParameters);

                // Construct Storage Manager
                Type[] genericStorageManagerParameters = new Type[] { dotNetKeyType, domainObjectType };
                Type storageManagerType = genericStorageManagerType.MakeGenericType(genericStorageManagerParameters);

                Type[] storageManagerConstructorArgs = new Type[] { dataProviderType };
                ConstructorInfo storageManagerConstructor = storageManagerType.GetConstructor(storageManagerConstructorArgs);

                // Construct Repository
                Type[] genericRepositoryParameters = new Type[] { dotNetKeyType, domainObjectType };
                Type repositoryType = genericRepositoryType.MakeGenericType(genericRepositoryParameters);

                Type[] repositoryConstructorArgs = new Type[] { storageManagerInterfaceType };
                ConstructorInfo repositoryConstructor = repositoryType.GetConstructor(repositoryConstructorArgs);

                entry = ConstructorCache.Current.SetConstructors_Transient(dotNetKeyType, databaseKeyType, domainObjectType, repositoryConstructor, storageManagerConstructor);
            }

            object storageManagerInstance = entry.StorageManagerConstructor_Transient.Invoke(new object[] { dataProvider });
            object repositoryInstance = entry.RepositoryConstructor_Transient.Invoke(new object[] { storageManagerInstance });
            return repositoryInstance;
        }

        public static object CreateDataSetBackedRepository(Type dotNetKeyType, Type databaseKeyType, Type domainObjectType, IDataProvider dataProvider, Expression domainObjectConstructionMethod, IDomainModel domainModel)
        {
            var entry = ConstructorCache.Current.GetEntry(dotNetKeyType, databaseKeyType, domainObjectType);
            if ((entry == null) || (!entry.HasConstructor_DataSet))
            {
                Type genericRepositoryType = typeof(Repository<,>);
                Type genericStorageManagerInterfaceType = typeof(IStorageManager<,>);
                Type genericDataSetSerializableInterfaceType = typeof(IDataSetSerializable<,>);
                Type genericStorageManagerType = typeof(DataSetFileStorageManager<,,>);
                Type genericDomainObjectConstructionMethodType = typeof(Func<>);
                Type genericDomainObjectConstructionExpressionType = typeof(Expression<>);
                Type dataProviderType = typeof(IDataProvider);
                Type dataSourceType = dataProvider.DataSourceType;
                Type dataSetType = typeof(DataSet);
                Type dataRowType = typeof(DataRow);

                // Get IStorageManager Type
                Type[] genericStorageManagerInterfaceParameters = new Type[] { dotNetKeyType, domainObjectType };
                Type storageManagerInterfaceType = genericStorageManagerInterfaceType.MakeGenericType(genericStorageManagerInterfaceParameters);

                // Get DomainObject construction method Type
                Type[] genericDomainObjectConstructionMethodParameters = new Type[] { domainObjectType };
                Type domainObjectConstructionMethodType = genericDomainObjectConstructionMethodType.MakeGenericType(genericDomainObjectConstructionMethodParameters);

                // Get DomainObject construction Expression Type
                Type[] genericDomainObjectConstructionExpressionParameters = new Type[] { domainObjectConstructionMethodType };
                Type domainObjectConstructionExpressionType = genericDomainObjectConstructionExpressionType.MakeGenericType(genericDomainObjectConstructionExpressionParameters);

                Type dataSetTypeToUse = dataSourceType;
                Type[] genericDataSetSerializableInterfaceParameters = new Type[] { dataSourceType, dataRowType };
                Type dataSetSerializableInterfaceType = genericDataSetSerializableInterfaceType.MakeGenericType(genericDataSetSerializableInterfaceParameters);
                if (!domainObjectType.GetInterfaces().Contains<Type>(dataSetSerializableInterfaceType))
                { dataSetTypeToUse = dataSetType; }

                // Construct Storage Manager
                Type[] genericStorageManagerParameters = new Type[] { dotNetKeyType, domainObjectType, dataSetTypeToUse };
                Type storageManagerType = genericStorageManagerType.MakeGenericType(genericStorageManagerParameters);

                Type[] storageManagerConstructorArgs = new Type[] { dataProviderType, domainObjectConstructionExpressionType, typeof(IBatchPersistableDomainModel) };
                ConstructorInfo storageManagerConstructor = storageManagerType.GetConstructor(storageManagerConstructorArgs);

                // Construct Repository
                Type[] genericRepositoryParameters = new Type[] { dotNetKeyType, domainObjectType };
                Type repositoryType = genericRepositoryType.MakeGenericType(genericRepositoryParameters);

                Type[] repositoryConstructorArgs = new Type[] { storageManagerInterfaceType };
                ConstructorInfo repositoryConstructor = repositoryType.GetConstructor(repositoryConstructorArgs);

                entry = ConstructorCache.Current.SetConstructors_DataSet(dotNetKeyType, databaseKeyType, domainObjectType, repositoryConstructor, storageManagerConstructor);
            }

            object storageManagerInstance = entry.StorageManagerConstructor_DataSet.Invoke(new object[] { dataProvider, domainObjectConstructionMethod, domainModel as IBatchPersistableDomainModel });
            object repositoryInstance = entry.RepositoryConstructor_DataSet.Invoke(new object[] { storageManagerInstance });
            return repositoryInstance;
        }

        public static object CreateEntityFrameworkBackedRepository(Type dotNetKeyType, Type databaseKeyType, Type domainObjectType, IDataProvider dataProvider, Expression domainObjectConstructionMethod, IDomainModel domainModel)
        {
            var entry = ConstructorCache.Current.GetEntry(dotNetKeyType, databaseKeyType, domainObjectType);
            if ((entry == null) || (!entry.HasConstructor_EntityFramework))
            {
                Type genericRepositoryType = typeof(Repository<,>);
                Type genericStorageManagerInterfaceType = typeof(IStorageManager<,>);
                Type genericStorageManagerType = typeof(EntityFrameworkDatabaseStorageManager<,,,>);
                Type dataProviderType = typeof(IDataProvider);
                Type dataSourceType = dataProvider.DataSourceType;

                // Get IStorageManager Type
                Type[] genericStorageManagerInterfaceParameters = new Type[] { dotNetKeyType, domainObjectType };
                Type storageManagerInterfaceType = genericStorageManagerInterfaceType.MakeGenericType(genericStorageManagerInterfaceParameters);

                // Construct Storage Manager
                Type[] genericStorageManagerParameters = new Type[] { dotNetKeyType, domainObjectType, databaseKeyType, dataSourceType };
                Type storageManagerType = genericStorageManagerType.MakeGenericType(genericStorageManagerParameters);

                Type[] storageManagerConstructorArgs = new Type[] { dataProviderType };
                ConstructorInfo storageManagerConstructor = storageManagerType.GetConstructor(storageManagerConstructorArgs);

                // Construct Repository
                Type[] genericRepositoryParameters = new Type[] { dotNetKeyType, domainObjectType };
                Type repositoryType = genericRepositoryType.MakeGenericType(genericRepositoryParameters);

                Type[] repositoryConstructorArgs = new Type[] { storageManagerInterfaceType };
                ConstructorInfo repositoryConstructor = repositoryType.GetConstructor(repositoryConstructorArgs);

                entry = ConstructorCache.Current.SetConstructors_EntityFramework(dotNetKeyType, databaseKeyType, domainObjectType, repositoryConstructor, storageManagerConstructor);
            }

            object storageManagerInstance = entry.StorageManagerConstructor_EntityFramework.Invoke(new object[] { dataProvider });
            object repositoryInstance = entry.RepositoryConstructor_EntityFramework.Invoke(new object[] { storageManagerInstance });
            return repositoryInstance;
        }
    }
}